package com.example.demo;

import XPStruct.C2SStruct;
import XPStruct.S2CExecV;
import XPStruct.S2CStruct;
import XPStruct.XProcessI;
import org.java_websocket.server.WebSocketServer;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.util.*;
import java.net.InetSocketAddress;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.atomic.AtomicBoolean;

// 定义一个回调接口
interface XPCallback {
    void onC2SComplete(C2SStruct result) throws InterruptedException;
}

public class XPWebSocket extends WebSocketServer {
    private static final Map<Integer, XPWebSocket> instances = new HashMap<>();

    public static XPWebSocket getInstance(int port) {
        return instances.get(port);
    }

    public static XPWebSocket installInstance(int port) {
        if (instances.containsKey(port)) {
            return instances.get(port);
        }
        XPWebSocket instance = new XPWebSocket(port);
        instances.put(port, instance);
        return instance;
    }

    // 最后执行错误堆栈
    public String lastEStackTrace = "";

    public XPWebSocket(int port) {
        super(new InetSocketAddress(port));
    }

    // XProcessI
    private String b64_en(String value) {
        return Base64.getEncoder().encodeToString(value.getBytes());
    };

    private String b64_de(String value) {
        return new String(Base64.getDecoder().decode(value));
    };

    private long long_de(String value) {
        return Long.parseLong(value);
    };

    private static String calculateMD5(String input) {
        try {
            // 创建 MD5 消息摘要实例
            MessageDigest md = MessageDigest.getInstance("MD5");

            // 计算哈希值
            byte[] messageDigest = md.digest(input.getBytes());

            // 将字节数组转换为十六进制字符串
            StringBuilder hexString = new StringBuilder();
            for (byte b : messageDigest) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    private void _logString(String log) {
        System.out.println(log);
    }

    private void _logFormat(String format, Object... args) {
        if (format == null) {
            return;
        }
        _logString(String.format(format, args));
    }

    private String _2FormatS(String format, Object... args) {
        if (format == null) {
            return "";
        }
        return String.format(format, args);
    }

    public static long _2TIMEStamp() {
        return System.currentTimeMillis();
    }

    public static String bytesToHex(byte[] bytes) {
        if (bytes == null) {
            return "";
        }
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xFF & b); // 将字节转换为无符号整型
            if (hex.length() == 1) {
                hexString.append('0'); // 如果长度为1，前面补0
            }
            hexString.append(hex);
        }
        return hexString.toString().toUpperCase(); // 转换为大写
    }

    // 记录最后使用的索引值
    long lastClientIndex = 1;

    // 获取索引值
    private synchronized long getClientIndex() {
        return ++lastClientIndex;
    }

    private ArrayList<XProcessI> TRemoteProcessS = new ArrayList<XProcessI>();

    private synchronized void WriteXProcessI(XProcessI xp) {
        TRemoteProcessS.add(xp);
    }

    private boolean InstallXRProcessI(ClientHandshake clientHandshake, XProcessI xProcessI) {
        // 遍历所有的消息头内容
        clientHandshake.iterateHttpFields().forEachRemaining(name -> {
            String value = clientHandshake.getFieldValue(name);

            if (name.equals("xDevice-id")) {
                xProcessI.device_udid = value;
                return;
            }
            if (name.equals("xDevice-name")) {
                xProcessI.device_name = b64_de(value);
                return;
            }
            if (name.equals("xDevice-type")) {
                xProcessI.device_type = value;
                return;
            }
            if (name.equals("xProcess-id")) {
                xProcessI._package = value;
                return;
            }
            if (name.equals("xProcess-exe")) {
                xProcessI.exen = b64_de(value);
                return;
            }
            if (name.equals("xProcess-name")) {
                xProcessI.name = b64_de(value);
                return;
            }

            if (name.equals("xProcess-uid")) {
                xProcessI.uid = long_de(value);
                return;
            }
            if (name.equals("xProcess-gid")) {
                xProcessI.gid = long_de(value);
                return;
            }

            if (name.equals("xProcess-pid")) {
                xProcessI.pid = long_de(value);
                return;
            }
            if (name.equals("xProcess-ppid")) {
                xProcessI.ppid = long_de(value);
                return;
            }

            if (name.equals("xProcess-res")) {
                xProcessI.path_install = b64_de(value);
                return;
            }
            if (name.equals("xProcess-home")) {
                xProcessI.path_sandbox = b64_de(value);
                return;
            }
        });

        // 是否处理失败
        if (xProcessI.Identifier().isEmpty())
            return false;
        if (xProcessI.device_udid.isEmpty())
            return false;
        String kString = "";
        kString += xProcessI.device_udid;
        kString += "_";
        kString += xProcessI.Identifier();
        kString += String.format("_%d_%d", xProcessI.gid,
                xProcessI.uid);
        xProcessI.uniqueID = calculateMD5(kString);
        //
        return !xProcessI.Identifier().isEmpty();
    }

    private String ClientHandshake2String(ClientHandshake clientHandshake) {
        StringBuilder retval = new StringBuilder();
        // 遍历所有的消息头内容
        clientHandshake.iterateHttpFields().forEachRemaining(name -> {
            String value = clientHandshake.getFieldValue(name);
            retval.append(String.format("%-25s : %s \n",
                    name, value));
        });
        return retval.toString();
    }

    // 关闭指定客户端的链接
    private synchronized void NETClose(String idString, String tipString) {
        ArrayList<WebSocket> kCloseWebSocketS = new ArrayList<WebSocket>();
        // 查找, 是否要关闭当前项
        getConnections().forEach(webSocket -> {
            String uniqueID = webSocket.getAttachment();
            if (null == uniqueID || uniqueID.isEmpty()) {
                return;
            }
            if (idString.equals("*") || uniqueID.equals(idString)) {
                kCloseWebSocketS.add(webSocket);
            }
        });
        // 主动关闭将要关闭的链接
        kCloseWebSocketS.forEach(webSocket -> {
            String uniqueID = webSocket.getAttachment();
            if (null == uniqueID || uniqueID.isEmpty()) {
                webSocket.close();
                return;
            }

            if (null != tipString && !tipString.isEmpty()) { // 存在提示时, 主动打印日志
                _logFormat("★注意★, 进程释放, uri = %s, %s",
                        uniqueID, tipString);
            }
        });
        // 移除配置
        TRemoteProcessS.removeIf(xp -> {
            if (idString.equals("*") || xp.uniqueID.isEmpty())
                return true;
            //
            return xp.uniqueID.equals(idString);
        });
    }

    @Override
    public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
        XProcessI xProcessI = new XProcessI(); // 检测连接时, 关键头数据是否存在
        // 格式化出错时, 主动关闭当前连接
        if (null == clientHandshake || !InstallXRProcessI(clientHandshake, xProcessI)) {
            assert clientHandshake != null;
            _logFormat("★注意★, 进程连接, uri = %s, 异常, 未传入必要参数\n%s\n",
                    webSocket.getRemoteSocketAddress().toString(),
                    ClientHandshake2String(clientHandshake));
            webSocket.close();
            return;
        }
        // 更新时间
        xProcessI.time_connect = _2TIMEStamp();
        xProcessI.time_heartbeat = _2TIMEStamp();

        // 清除历史链接
        NETClose(xProcessI.uniqueID, "新链接已启动");

        // 记录连接索引
        xProcessI.connect_index = getClientIndex();
        // 记录远程地址
        xProcessI.uri = webSocket.getRemoteSocketAddress().toString();

        // 追加到全局配置中
        WriteXProcessI(xProcessI);
        // 设置当前 webClient 的 唯一ID 值
        webSocket.setAttachment(xProcessI.uniqueID);

        // 打印链接日志
        String tipString = "";
        tipString += _2FormatS("☆设备信息 = %-4d, %-7s, %-40s, %s\n", xProcessI.connect_index, xProcessI.device_type,
                xProcessI.device_udid, xProcessI.device_name);
        tipString += _2FormatS("☆应用信息 = %-30s, %s\n", xProcessI._package, xProcessI.name);
        tipString += _2FormatS("☆沙盒路径 = %s\n", xProcessI.path_sandbox);
        tipString += _2FormatS("☆安装路径 = %s\n", xProcessI.path_install);
        tipString += _2FormatS("☆进程权限 = %-10s, %-6d(组) , %-5d(用户)\n", "",
                xProcessI.gid, xProcessI.uid);
        tipString += _2FormatS("☆进程信息 = %-10s, %-6d(pid), %-5d(ppid)\n", xProcessI.exen,
                xProcessI.pid, xProcessI.ppid);
        tipString += _2FormatS("☆网络地址 = %s\n", xProcessI.uri);
        //
        _logFormat("★注意★, 进程上线, uri = %s, 应用名称 = %-15s, 连接时间 = %d\n%s", xProcessI.uniqueID,
                xProcessI.XProcessNAME(), xProcessI.time_connect, tipString);
    }

    @Override
    public void onClose(WebSocket webSocket, int i, String s, boolean b) {
        String IDString = webSocket.getAttachment();
        // 一般不为空
        if (null == IDString || IDString.isEmpty()) {
            return;
        }
        // 记录日志
        _logFormat("★注意★, 进程离线, uri = %s, 退出代码 = %d", IDString, i);
        _logFormat("★注意★, 进程离线, uri = %s, 退出原因 = %s", IDString, s);
        _logFormat("★注意★, 进程离线, uri = %s, 远程关闭 = %s", IDString, b ? "true" : "false");
        // 清除历史备份
        NETClose(IDString, null);
    }

    private ArrayList<C2SStruct> TXProcessMessageS = new ArrayList<C2SStruct>();

    private final Map<String, XPCallback> TXPMessageCBS = new HashMap<>();

    private synchronized void WriteXPMessageI(String IDString, C2SStruct msg) throws InterruptedException {
        // 打印收到的结构日志
        _logFormat("★注意★, 进程消息, uri = %s, 消息主体.action = %s", IDString, msg.action);
        _logFormat("★注意★, 进程消息, uri = %s, 消息主体.guid   = %s", IDString, msg.guid);
        _logFormat("★注意★, 进程消息, uri = %s, 消息主体.kid    = %s", IDString, msg.kid);
        _logFormat("★注意★, 进程消息, uri = %s, 消息主体.time   = %d", IDString, msg.time);
        _logFormat("★注意★, 进程消息, uri = %s, 消息主体.error  = %s", IDString, msg.error);
        _logFormat("★注意★, 进程消息, uri = %s, 消息主体.retval = %s", IDString, bytesToHex(msg.retv));
        _logFormat("\r\n");
        //
        boolean ISXCallback = false;
        // 追加到全局 or 触发回调
        for (Map.Entry<String, XPCallback> entry : TXPMessageCBS.entrySet()) {
            if (entry.getKey().equals(msg.guid) && null != entry.getValue()) {
                entry.getValue().onC2SComplete(msg);

                ISXCallback = true;
                break;
            }
        }
        // 已使用回调, 不做全局记录
        if (!ISXCallback)
            TXProcessMessageS.add(msg);
        // 移除回调记录
        if (ISXCallback)
            TXPMessageCBS.remove(msg.guid);
    }

    @Override
    public void onMessage(WebSocket webSocket, String message) {
        String IDString = webSocket.getAttachment();
        // 一般不为空
        if (null == IDString || IDString.isEmpty()) {
            return;
        }
        //
        _logFormat("★注意★, 未知消息, uri = %s, 消息内容 = %s", IDString,
                message);
    }

    //
    public void onMessage(WebSocket webSocket, ByteBuffer message) {
        String IDString = webSocket.getAttachment();
        // 一般不为空
        if (null == IDString || IDString.isEmpty()) {
            return;
        }
        //
        _logFormat("★注意★, 进程消息, uri = %s, 消息内容 = %s", IDString,
                bytesToHex(message.array()));
        //
        try {
            WriteXPMessageI(IDString, new C2SStruct(message));
        } catch (InterruptedException e) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);
            lastEStackTrace = pw.toString();
        }
    }

    @Override
    public void onError(WebSocket webSocket, Exception e) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        lastEStackTrace = pw.toString();
    }

    @Override
    public void onStart() {

    }

    // 关闭指定客户端的链接
    public ArrayList<XProcessI> GetXprocessS() {
        return TRemoteProcessS;
    }

    // 向指定进程, 发送消息
    public synchronized S2CExecV NETSendAction2XP(String uniqueID, String kAction, XPCallback xCallback,
            Object... args) {
        S2CStruct TMPStruct = new S2CStruct();
        // 发送结构体
        TMPStruct.action = kAction;
        TMPStruct.guid = S2CStruct.guid();
        TMPStruct.kid = uniqueID;
        TMPStruct.time = _2TIMEStamp();
        TMPStruct.args = S2CStruct.MSGPackParams(args);

        try {
            // 参数转成文本型
            byte[] argS = TMPStruct.serialize();
            if (null == args || 0 == argS.length) {
                return new S2CExecV(null, "序列化失败");
            }
            // 查找链接状态
            AtomicBoolean FStatus = new AtomicBoolean(false);

            // 发送之前, 优先设置回调函数
            if (xCallback != null)
                TXPMessageCBS.put(TMPStruct.guid, xCallback);
            // 查找, 是否要关闭当前项
            getConnections().forEach(webSocket -> {
                String uid = webSocket.getAttachment();
                if (null == uid || uid.isEmpty())
                    return;
                if (!uniqueID.equals(uid))
                    return;
                // 是否已找到
                FStatus.set(true);
                // 发送本次的请求内容
                webSocket.send(argS);
                //
                _logFormat("★注意★, 接口调用, uri = %s, 消息主体.action = %s", uid, TMPStruct.action);
                _logFormat("★注意★, 接口调用, uri = %s, 消息主体.guid   = %s", uid, TMPStruct.guid);
            });

            if (!FStatus.get()) // 是否找到
                return new S2CExecV(null, String.format("★注意★, 未找到指定进程, %s", uniqueID));

            // 成功, 返回guid, 失败, 返回空文本
            return new S2CExecV(TMPStruct.guid, null);
        } catch (Exception e) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);
            lastEStackTrace = pw.toString();
            return new S2CExecV(null, "执行异常, 错误代码 = " + e.getMessage());
        }
    }

    // 查找数据包, 是否已返回
    public synchronized C2SStruct NETRecvActionMSG(String guid) {
        // 记录当前时间, 用于删除超时消息
        long ltime = _2TIMEStamp();
        // 返回值, 临时值
        C2SStruct TMPStruct = new C2SStruct();
        // 从全局队列中, 查找关键数据
        for (C2SStruct msg : TXProcessMessageS) {
            if (!msg.guid.equals(guid))
                continue;
            // 修改返回值
            TMPStruct = msg;
            //
            break;
        }
        // 移除历史结果
        TXProcessMessageS.removeIf(msg -> {
            return msg.guid.equals(guid);
        });
        TXProcessMessageS.removeIf(msg -> {
            return ltime - msg.time >= 30 * 1000;
        });
        // 返回
        return Objects.equals(TMPStruct.guid, guid) ? TMPStruct : null;
    }
}
